/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package GUI;

import Domain.GameGrid;
import Domain.Player.Disc;
import Main.AppConstants;
import java.awt.*;
import java.awt.event.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;

/**
 *
 * @author Mattis
 */
public class GameBoard extends JPanel implements Serializable{

        // Locks:
        private boolean animDone = true;
        private Lock lock = new ReentrantLock();
        private Condition animRunning = lock.newCondition();
        // Instances:
        private GUIController guiController;
        private JLayeredPane gameBoardPane;
        // Components:
        private JLabel board[][] = new JLabel[GameGrid.GRID_ROWS][GameGrid.GRID_COLUMNS];
        private final ImageIcon CELL_OVERLAY = new ImageIcon(getClass().getClassLoader().getResource(AppConstants.IMAGE_BOARD));
        private final ImageIcon DISC_EMPTY = new ImageIcon(getClass().getClassLoader().getResource(AppConstants.IMAGE_DISC_EMPTY));
        // Size of components:
        private final int CELL_SIZE = 80;

        /** GameBoard constructor */
        public GameBoard(GUIController guiController) {
                this.guiController = guiController;

                setLayout(new BorderLayout());

                createGameBoard();
        }

        /** Create game board */
        private void createGameBoard() {
                gameBoardPane = new JLayeredPane();
                gameBoardPane.setBorder(new EmptyBorder(0, 0, 0, 0));
                gameBoardPane.setLayout(new GridLayout(GameGrid.GRID_ROWS, GameGrid.GRID_COLUMNS, 0, 0));

                addBoardOverlay();
                addGameBoardCells();

                add(gameBoardPane);
        }

        /** Add the game board "frame" to the panel */
        private void addBoardOverlay() {
                setBackground(AppConstants.COLOR_BOARD_BACKGROUND);
                /*for (int i = 0; i < GameGrid.GRID_ROWS; i++) {
                        for (int j = 0; j < GameGrid.GRID_COLUMNS; j++) {
                                JLabel tempLabel = new JLabel();
                                tempLabel.setIcon(CELL_OVERLAY);
                                tempLabel.setBounds(j * CELL_SIZE, i * (CELL_SIZE - 2), CELL_SIZE, CELL_SIZE);
                                add(tempLabel);
                        }
                }/**/
        }

        /** Add game board cells to the game board */
        private void addGameBoardCells() {
                for (int i = 0; i < GameGrid.GRID_ROWS; i++) {
                        for (int j = 0; j < GameGrid.GRID_COLUMNS; j++) {
                                JLabel cell = new JLabel();
                                cell.setName(i + " " + j);
                                cell.setPreferredSize(new Dimension(CELL_SIZE, CELL_SIZE));
                                cell.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
                                cell.addMouseListener(new HumanListener(guiController.getLogicController()));
                                board[i][j] = cell;
                                gameBoardPane.add(cell);
                        }
                }
        }

        /** Returns the game board data */
        public JLabel[][] getGameBoard() {
                return board;
        }

        /** Reset the game board */
        public void resetGameBoard() {
                JLabel cell;

                for (int i = 0; i < GameGrid.GRID_ROWS; i++) {
                        for (int j = 0; j < GameGrid.GRID_COLUMNS; j++) {
                                cell = (JLabel) board[i][j];
                                cell.setIcon(DISC_EMPTY);
                        }
                }
        }

        /** Add a disc to the game board usign an animation */
        public void animateDisc(int column) {
                lock.lock();
                try {
                        while (animDone == false) {
                                animRunning.await();
                        }
                } catch (InterruptedException ex) {
                        ex.printStackTrace();
                } finally {
                        lock.unlock();
                }
                animDone = false;
                new AnimateDisc(column, guiController.getCurrentPlayer().getDiscColor());
        }

        /** Updates the game board */
        public void updateGameBoard() {
                drawGameBoard();
        }

        /** Draw discs on the game board based on information from the game grid */
        private void drawGameBoard() {
                GameGrid currentGrid = guiController.getGameGrid();
                JLabel current;

                for (int i = 0; i < GameGrid.GRID_ROWS; i++) {
                        for (int j = 0; j < GameGrid.GRID_COLUMNS; j++) {
                                current = (JLabel) board[i][j];
                                if (currentGrid.getDisc(i, j) == Disc.ONE) {
                                        current.setIcon(new DiscIcon(guiController.getPlayer(1).getDiscColor()));
                                } else if (currentGrid.getDisc(i, j) == Disc.TWO) {
                                        current.setIcon(new DiscIcon(guiController.getPlayer(2).getDiscColor()));
                                } else current.setIcon(DISC_EMPTY);
                        }
                }
                repaint();
        }

        /** Presents the winner board */
        public void presentWinnerBoard(final Iterator winnerPointsItr) {
                List<Point> winnerCells = new ArrayList<Point>();
                while (winnerPointsItr.hasNext()) {
                        winnerCells.add((Point) winnerPointsItr.next());
                }
                if (winnerCells.size() > 0) {
                        flashWinnerCells(winnerCells);
                }
        }

        /** Flash the winner cells of the board. The method creates a new thread
         *  for the "animation", so that it's smooth. */
        private void flashWinnerCells(final List<Point> winnerCells) {
                new Thread(new Runnable() {

                        @Override
                        public void run() {
                                int count = 0;
                                Color color = AppConstants.COLOR_DISC_FLASH, orgColor = null;
                                while (count < AppConstants.NUM_WIN_FLASHES) {
                                        // Loop through the winnerRows iterator:
                                        for (int i = 0; i < winnerCells.size(); i++) {
                                                JLabel label = (JLabel) board[winnerCells.get(i).x][winnerCells.get(i).y];
                                                if (label.getIcon() instanceof DiscIcon) {
                                                        DiscIcon disc = (DiscIcon) label.getIcon();
                                                        if (orgColor == null) {
                                                                orgColor = disc.getColor();
                                                        }
                                                        changeDiscColor(disc, color);
                                                }
                                        }
                                        repaint();
                                        count++;
                                        if (orgColor != null) {
                                                color = color.equals(orgColor)
                                                                ? AppConstants.COLOR_DISC_FLASH : orgColor;
                                        }
                                }
                        }
                }).start();
        }

        /** Changes the disc to a certain color */
        private void changeDiscColor(DiscIcon disc, Color color) {
                try {
                        Thread.sleep(AppConstants.FLASH_DELAY);
                } catch (InterruptedException ex) {
                        System.out.println("ex" + ex);
                }
                disc.setColor(color);
        }

        /** Returns the index for the first free cell in the provided column.
         *  If the column is full -1 is returned. */
        private int getFreeRow(int column) {
                for (int row = GameGrid.GRID_ROWS - 1; row >= 0; row--) {
                        if (board[row][column].getIcon().toString().endsWith(AppConstants.IMAGE_DISC_EMPTY)) {
                                return row;
                        }
                }
                return -1;
        }

        /**
         * AnimatedDisc class allows animation for the disc when adding a new disc
         * to the board. Meanwhile the animation is played the class locks certain
         * methods so a client can't play multiple animations as the same time.
         *
         * The class is private because it needs the locks, but also needs to
         * repaint the GameBoard upon animating the disc.
         */
        private class AnimateDisc {

                // Column & disc:
                private int column;
                private JLabel animatedDisc;

                public AnimateDisc(int column, Color discColor) {
                        this.column = column;
                        animatedDisc = new JLabel(new DiscIcon(discColor));
                        startAnimation();
                }

                /** Starts the animation */
                private void startAnimation() {
                        // Lock:
                        lock.lock();
                        try {
                                animDone = true;
                                animRunning.signalAll();

                                int row = getFreeRow(column);
                                if (row > -1) {
                                        // 1. Add a new disc that we can animate:
                                        add(animatedDisc);

                                        // 2. Start & end positions:
                                        int xCoord = column * CELL_SIZE;                                // column * CELL_SIZE
                                        double yCoord = 0;                                                              // current Y coordinate of the animatedDisc
                                        int yCoordEnd = row * CELL_SIZE;                                // relative px from the game board
                                        int distance = yCoordEnd - (int)yCoord;                 // move distance [px]

                                        // 3. FPS independent movement speed:
                                        int animTime = AppConstants.FALL_TIME * 10000;  // animation time
                                        long oldTime = System.currentTimeMillis();              // start time
                                        long newTime = oldTime + 1;                                             // current time
                                        double pxFrame;
                                        double moves;

                                        // 4. Animation loop:
                                        while (yCoord < distance) {
                                                newTime = System.currentTimeMillis() + 1;
                                                moves = (double) (animTime / (newTime - oldTime));
                                                pxFrame = distance / moves;
                                                yCoord += pxFrame;

                                                animatedDisc.setBounds(xCoord, (int)yCoord, CELL_SIZE, CELL_SIZE);
                                                animatedDisc.repaint();
                                        }

                                        // 5. Remove animated disc:
                                        remove(animatedDisc);
                                }
                        } finally {
                                lock.unlock();
                        }
                }
        }
}